#include <list>
#include <map>
#include <iostream>
#include <fstream>
#include "Renderer.h"
#include "ProjectHeader.h"
#include "CatalogueLoader.h"
#include "SOIL\src\SOIL.h"	// Header File For The SOIL Library

			Renderer::Renderer()
{
	Translation.x = -2.0f;
	Translation.y = -1.0f;
	Translation.z = -5.0f;

	Speed.x = 0.2f;
	Speed.y = 0.3f;
	Speed.z = 0.5f;

	vboPointers.resize(3);
	DrawList.resize(3);
	elementCount.resize(3);

	light = false;
	xView = 0.0f;

	LightAmbient[0] = 0.5f; 
	LightAmbient[1] = 0.5f; 
	LightAmbient[2] = 0.5f; 
	LightAmbient[3] = 1.0f;

	LightDiffuse[0] = 1.0f; 
	LightDiffuse[1] = 1.0f;
	LightDiffuse[2] = 1.0f; 
	LightDiffuse[3] = 1.0f;

	LightPosition[0]= 0.0f; 
	LightPosition[1]= 0.0f; 
	LightPosition[2]= 2.0f; 
	LightPosition[3]= 1.0f;

	DrawList.resize(3);
	elementCount.resize(3);
}

int			Renderer::InitGL()							
{
	// All Setup For OpenGL Goes Here
	glEnable(GL_TEXTURE_2D);				// Enable Texture Mapping

	TextureLoader_delegate TexLoader(this, &Renderer::LoadTexture);	//Create a fastDelegate to LoadTexture
	if(CatalogueLoader::LoadTextureAtlases("Default.cat", TexLoader, TexCoords))	// Load up the texture atlases
	//TODO texture atlas name should be passed somehow
	{return TEX_LOAD_ERROR;}				// Or fail;

	if(!Texture.size()){return TEX_LOAD_ERROR;}	//Return a failure if no texture is loaded.

	for(int i = 0; i < 3; i++)
	{
		vboPointers[i].resize(Texture.size());
		elementCount[i].resize(Texture.size());
			for(int j = 0; j < Texture.size(); j++)
		{
			vboPointers[i][j] = 0;
		}
	}


	if(glewInit() != GLEW_OK) {return GLEW_LOAD_ERROR;}						
	// If GLEW fails to init, return a fail.

	//if(GLEW_VERSION_1_5){return GL_VERSION_ERROR;}
	// GL 1.5 is required for VBOs; if it's not available, return a fail.

	glLightfv(GL_LIGHT1, GL_AMBIENT,  LightAmbient);
	glLightfv(GL_LIGHT1, GL_DIFFUSE,  LightDiffuse);
	glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
	glEnable(GL_LIGHT1);
	glEnable(GL_LIGHTING);

	glShadeModel(GL_SMOOTH);						// Enables Smooth Shading
	glClearDepth(1.0f);								// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);						// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);							// Use <= depth tests
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	
	// Really Nice Perspective Calculations
	glClearColor(0.7f, 0.9f, 1.0f, 1.0f);			// Set a Background Colour

	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);	
	// Blend based on alpha value
	glEnable(GL_BLEND);
	char * Version = (char *) glGetString(GL_VERSION);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);	
	glEnableClientState(GL_COLOR_ARRAY);
	return 0;	// Initialization Went OK
	}

GLvoid		Renderer::ReSizeGLScene(GLsizei width, GLsizei height)
{
	if (height==0)							// Prevent A Divide By Zero By
	{height=1;}								// Making Height Equal One

	glViewport(0,0,width,height);			// Reset The cxCurrent Viewport

	glMatrixMode(GL_PROJECTION);			// Select The Projection Matrix
	glLoadIdentity();						// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);

	glMatrixMode(GL_MODELVIEW);				// Select The Modelview Matrix
	glLoadIdentity();						// Reset The Modelview Matrix
	char *version = (char *)glGetString(GL_VERSION);

}

void		Renderer::ReSizeGLScene(UINT msg, WPARAM wParam, LPARAM lParam)
{
	GLsizei width = LOWORD(lParam);			// The low word contains the width
	GLsizei height = HIWORD(lParam);		// The high word contains the height.
	ReSizeGLScene(width, height);			// Call overloaded ReSizeGLScene function
}

int			Renderer::DrawGLScene(GLvoid)
{
	// Here's Where We Do All The Drawing

	UpdateVBOs();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
	glLoadIdentity();									// Reset the Modelview Matrix

	glTranslatef(	Translation.x + xView, Translation.y, (Translation.z + depth));	
	// Re-apply the view transform from the top

	glRotatef(	Rotation.x,		1.0f,	0.0f,	0.0f);	// Rotate by xRot along x axis
	glRotatef(	Rotation.y,		0.0f,	1.0f,	0.0f);	// Rotate by yRot along y axis
	glRotatef(	Rotation.z,		0.0f,	0.0f,   1.0f);	// Rotate by zRot along z axis

	for(unsigned int AtlasIndex = 0; AtlasIndex < Texture.size(); AtlasIndex++)
	//for(unsigned int AtlasIndex = 0; AtlasIndex < 1; AtlasIndex++)
	{
		if(CurrentBoundTex != Texture[AtlasIndex]){glBindTexture(GL_TEXTURE_2D, Texture[AtlasIndex]); CurrentBoundTex = Texture[AtlasIndex];}
		for (unsigned int VBOPerfType = 0; VBOPerfType < elementCount.size(); VBOPerfType++)
		{
			GLuint FirstVertex = 0;
			for(unsigned int VBOElement = 0; VBOElement < elementCount[VBOPerfType][AtlasIndex].size() && elementCount[VBOPerfType][AtlasIndex].size(); VBOElement++)
			{
				if(DrawList[VBOPerfType][VBOElement].Transform != NULL)
				{
					glPushMatrix();
					Transform temp;
					temp = DrawList[VBOPerfType][AtlasIndex].Transform();
					glTranslatef(temp.Translation.x, temp.Translation.y, temp.Translation.z);
					glRotatef(temp.Rotation.x, 1.0f, 0.0f, 0.0f);
					glRotatef(temp.Rotation.y, 0.0f, 1.0f, 0.0f);
					glRotatef(temp.Rotation.z, 0.0f, 0.0f, 1.0f);
				}
				glBindTexture(GL_TEXTURE_2D, Texture[AtlasIndex]);
				glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboPointers[VBOPerfType][AtlasIndex]);
				glVertexPointer(	3,	GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 0);
				glNormalPointer(		GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 12);
				glTexCoordPointer(	2,	GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 24);
				glColorPointer(		4,	GL_FLOAT, sizeof(vertex),	(const GLubyte *)0 + 32);
				glDrawArrays(GL_TRIANGLES, FirstVertex, elementCount[VBOPerfType][AtlasIndex][VBOElement]);
				FirstVertex += elementCount[VBOPerfType][AtlasIndex][VBOElement];
				if(DrawList[VBOPerfType][VBOElement].Transform != NULL){glPopMatrix();}
			}
		}
	}

	//glDisableClientState(GL_VERTEX_ARRAY);
	//glDisableClientState(GL_NORMAL_ARRAY);
	//glDisableClientState(GL_TEXTURE_COORD_ARRAY);	
	//glDisableClientState(GL_COLOR_ARRAY);

	
	GLenum error;
	for(error = glGetError(); error != GL_NO_ERROR; )
	{errors.push_back(error);}				// Records any GL errors

	Rotation.x += Speed.x;					// Rotates the GL matrix
	Rotation.y += Speed.y;					// Per Frame
	Rotation.z += Speed.z;

	return TRUE;							// Everything Went OK
}

int			Renderer::LoadTexture(const char *File)
{
	if(File == NULL){return NULL_FILE_ERROR;}
	Texture.push_back(SOIL_load_OGL_texture(File, 4, 0, SOIL_FLAG_POWER_OF_TWO));
	//TODO Look at other flags and whatnot for SOIL_load_OGL_texture
	if(Texture.back()){return Texture.size() -1;}
	//Texture.size() - 1 is the last index; the one that we just put there.
	Texture.pop_back();	//Remove the entry that we added; There was an error.
	return SOIL_TEX_LOAD_ERROR;

}

GLvoid		Renderer::toggleLight()
{
	light = !light;
	if(light)
	{
		glEnable(GL_LIGHTING);
	}
	else
	{
		glDisable(GL_LIGHTING);
	}
}

GLvoid		Renderer::toggleBlend()
{
	blend = !blend;
	if(blend)
	{
		glEnable(GL_BLEND);
		glDisable(GL_DEPTH_TEST);
	}
	else
	{
		glDisable(GL_BLEND);
		glEnable(GL_DEPTH_TEST);
	}
}

bool		Renderer::RegisterDraw(DrawEventPair Drawer, enum DRAW_TYPE type)
{
	DrawList[type].push_back(Drawer); if (type == STATIC){redrawStatics = true;}
	return true;
}

bool		Renderer::RegisterDraw(DrawEvent_delegate Drawer, enum DRAW_TYPE type)
{
	DrawEventPair tempPair;
	tempPair.Transform = NULL;
	tempPair.Draw = Drawer;
	return (RegisterDraw(tempPair, type));
}

void		Renderer::UpdateVBOs()
{
	for(int VBOPerfType = 0; VBOPerfType < 3 && (VBOPerfType != STATIC || redrawStatics) && DrawList[VBOPerfType].size(); VBOPerfType++)
	{
		GLenum Usage; 
			 if(VBOPerfType == 0){Usage = GL_STATIC_DRAW;}
		else if(VBOPerfType == 1){Usage = GL_DYNAMIC_DRAW;}
		else if(VBOPerfType == 2){Usage = GL_STREAM_DRAW;}
		elementCount[VBOPerfType].clear();
		elementCount[VBOPerfType].resize(Texture.size());
		redrawStatics = false;
		for(DrawEventIter Drawer = DrawList[VBOPerfType].begin(); Drawer != DrawList[VBOPerfType].end(); ++Drawer)
		{
			for(int AtlasIndex = 0; AtlasIndex < Texture.size(); AtlasIndex++)
			{
				std::vector<vertex> *TempVBO = new std::vector<vertex>;
				GLsizei OldSize = 0;
				(*Drawer).Draw(TempVBO, TexCoords, AtlasIndex);
				elementCount[VBOPerfType][AtlasIndex].push_back(TempVBO->size() - OldSize);
				OldSize = TempVBO->size();
				if(TempVBO->size())
				{
					if(!vboPointers[VBOPerfType][AtlasIndex]){ glGenBuffersARB(1, &vboPointers[VBOPerfType][AtlasIndex]); }
					glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboPointers[VBOPerfType][AtlasIndex]);
					glBufferDataARB(GL_ARRAY_BUFFER_ARB, TempVBO->size() * sizeof(vertex), &(TempVBO->at(0).position.x), Usage);
				}
				delete TempVBO;
			}
		}
	}
}
const std::map<int, texCoord2f>& Renderer::getTexCoords()
{
	return TexCoords;
}